Result & Option
Option<T> — Handling “maybe a value”
enum Option<T> {
Some(T),
None,
}
Use Option<T> when:
- A value might be present.
- Absence is normal and not an error (e.g., searching, optional fields).
Example 1: Finding a value in a list
fn find_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
let nums = vec![1, 3, 5, 8, 9];
match find_even(&nums) {
Some(value) => println!("Found even number: {}", value),
None => println!("No even number found"),
}
}
Some(value)→ success.None→ nothing found.- The compiler forces you to handle both cases.
Common Option methods
unwrap() (may panic)
let x = Some(10);
println!("{}", x.unwrap()); // prints 10
unwrap_or()
let x: Option<i32> = None;
println!("{}", x.unwrap_or(0)); // prints 0
if let shorthand
if let Some(value) = find_even(&nums) {
println!("Found: {}", value);
}
Result<T, E> — Handling recoverable errors
enum Result<T, E> {
Ok(T),
Err(E),
}
Use Result<T, E> when:
- An operation might fail.
- You want to explain why it failed.
Example 2: Safe division
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
Ok(result)→ success.Err(error_message)→ failure with reason.
Example 3: Reading a file
use std::fs::File;
use std::io::{self, Read};
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // may fail
let mut contents = String::new();
file.read_to_string(&mut contents)?; // may fail
Ok(contents)
}
fn main() {
match read_file("hello.txt") {
Ok(text) => println!("File contents:\n{}", text),
Err(e) => println!("Failed to read file: {}", e),
}
}
?propagates errors automatically.- The function returns
Result<String, io::Error>.
Converting between Option and Result
Option → Result
let value: Option<i32> = None;
let result = value.ok_or("Value was missing");
Result → Option
let result: Result<i32, &str> = Ok(5);
let option = result.ok();
Option vs Result — When to use which
Use Option<T> when… | Use Result<T, E> when… |
|---|---|
| Value may or may not exist | Operation may fail |
| Absence is normal | You need an error reason |
| No error details needed | Error details matter |
The ? operator (works with both!)
With Result:
fn get_number() -> Result<i32, String> {
let n = divide(10, 2)?; // if Err, returns early
Ok(n * 2)
}
With Option:
fn double_first_even(nums: &[i32]) -> Option<i32> {
let even = find_even(nums)?; // if None, returns None
Some(even * 2)
}
Summary
Option<T>= “There might be a value.”Result<T, E>= “This might fail, and here’s why.”- Both force you to handle errors at compile time.
- Use
match,if let,unwrap_or, or?to handle them cleanly. - Prefer
Resultoverpanic!for recoverable errors.